#include "GLWidget.h"
#include "MainWindow.h"
#include "../Core/Exceptions.h"
#include "../Core/Materials.h"

#include "../Core/Constants.h"


#include <fstream>
#include <QFileDialog>
using namespace std;

namespace cagd
{
    // default/special constructor
    GLWidget::GLWidget(QWidget *parent, const QGLFormat &format): QGLWidget(format, parent)
    {
        timer = new QTimer(this);
        timer->setInterval(0);

        connect(timer, SIGNAL(timeout()), this, SLOT(animate()));

        _parent = parent;
    }

    // destructor
    GLWidget::~GLWidget()
    {
        if (_image_of_data_points)
        {
            delete _image_of_data_points, _image_of_data_points = 0;
        }

        // free every dynamically allocated object
        if (_cc)
        {
            delete _cc, _cc = 0;
        }

        if (_image_cc)
        {
            delete _image_cc, _image_cc = 0;
        }

        if (_icc)
        {
            delete _icc, _icc = 0;
        }

        if (_image_icc)
        {
            delete _image_icc, _image_icc = 0;
        }

        if (_population) {
            delete _population, _population = 0;
        }
    }

    // initializer method
    // it is called once before the first call to paintGL() or resizeGL(),
    // and then once whenever the widget has been assigned a new QGLContext
    void GLWidget::initializeGL()
    {
        glewInit();

        // define a projection matrix
        glMatrixMode(GL_PROJECTION);

        _aspect = (GLfloat)width() / (GLfloat)height();
        _fovy = 45.0;
        _z_near = 1.0;
        _z_far = 1000.0;

        glLoadIdentity();
        gluPerspective(_fovy, _aspect, _z_near, _z_far);

        // define a model view matrix
        glMatrixMode(GL_MODELVIEW);

        _eye[2] = 6.0;
        _up[1] = 1.0;

        glLoadIdentity();
        gluLookAt(_eye[0], _eye[1], _eye[2], _center[0], _center[1], _center[2], _up[0], _up[1], _up[2]);

        // enable the required OpenGL states

        // enable depth testing
        glEnable(GL_DEPTH_TEST);

        // ensure unit normal vectors
        glEnable(GL_NORMALIZE);

        // enable default lighting
        //glEnable(GL_LIGHTING);

        // enable default light source 0, which is a directional light with grey ambient, diffuse and specular intensities
        glEnable(GL_LIGHT0);

        // set the background color
        glClearColor(0.0, 0.0, 0.0, 1.0);

        //  create/load your geometry

        if (!_mouse.LoadFromOFF("Models/mouse.off", GL_TRUE))
        {
            throw Exception("Could not load the model file!");
        }
        else
        {
            if (!_mouse.UpdateVertexBufferObjects())
            {
                throw Exception("Could not create the vertex buffer object of the model!");
            }
        }

        _trans_x = _trans_y = _trans_z = 0.0;
        _scale = 1.0;
        _angle_x = _angle_y = _angle_z = 0.0;


        _image_cc = 0;
        _image_icc = 0;
        // create a cyclic curve
        _cc = 0;
        //        _n = 1;
        //        _cc = new CyclicCurve3(_n);

        //        if (!_cc)
        //        {
        //            throw Exception("Could not create cyclic curve!");
        //        }
        //        else
        //        {

        //            GLdouble step = 2.0 * PI / (2 * _n + 1);

        //            for (GLuint i = 0; i <= 2*_n; ++i)
        //            {
        //                DCoordinate3 &ref = (*_cc)[i];

        //                GLdouble u = i * step;

        //                ref[0] = 1.0 * cos(u);
        //                ref[1] = 1.0 * sin(u);
        //                ref[2] = 0.0;//-2.0 + 4.0 * (GLdouble)rand() / (GLdouble)RAND_MAX;
        //            }


        //            cout << "Length of the curve equals " << _cc->Length(100) << endl;

        //            if (!_cc->UpdateVertexBufferObjectsOfData())
        //            {
        //                throw Exception("Could not generate the VBO of the control polygon!");
        //            }

        //            cout << "Cyclic curve was created!\n";
        //            _max_order_of_derivatives = 2;
        //            _div_point_count = 500;
        //            _image_cc = _cc->GenerateImage(_max_order_of_derivatives, _div_point_count);

        //            if (!_image_cc)
        //            {
        //                throw Exception("Could not create the image of the cyclic curve!");
        //            }
        //            else
        //            {
        //                cout << "Image of cyclic curve was created!\n";
        //                if (!_image_cc->UpdateVertexBufferObjects())
        //                {
        //                    throw Exception("Could not create the vertex buffer object of the image!");
        //                }
        //            }
        //        }

        _icc = 0;
        //        _icc = new CyclicCurve3(_n);

        //        if (!_icc)
        //        {
        //            throw Exception("Could not create interpolating cyclic curve!");
        //        }
        //        else
        //        {

        //            // define knot values that will be associated with data points to be interpolated
        //            cout << _data_points_to_interpolate << "; " << _knot_vector << endl;
        //            _knot_vector.ResizeRows(2 * _n + 1);

        //            // e.g., you can use uniform parametrization
        //            GLdouble step = 2.0 * PI / (2 * _n + 1);
        //            for (GLuint i = 0; i <= 2 * _n; ++i)
        //            {
        //                _knot_vector[i] = i * step;
        //            }

        //            // define data points to be interpolated
        //            _data_points_to_interpolate.ResizeRows(2 * _n + 1);

        //            for (GLuint i = 0; i < 2 * _n + 1; ++i)
        //            {
        //                // we intend to interpolate the control polygon of the original cyclic curve
        //                _data_points_to_interpolate[i] = (*_cc)[i];
        //            }


        //            cout << "now\n" << _data_points_to_interpolate << "\n; " << _knot_vector << endl;

        //            if (!_icc->UpdateDataForInterpolation(_knot_vector, _data_points_to_interpolate))
        //            {
        //                throw Exception("Could not solve the curve interpolation problem!");
        //            }
        //            else
        //            {
        //                cout << "Length of the Icurve equals " << _icc->Length(100) << endl;
        //                if (!_icc->UpdateVertexBufferObjectsOfData())
        //                {
        //                    throw Exception("Could not generate the control polygon of the interpolating cyclic curve!");
        //                }

        //                cout << "ICyclic curve was created!\n";
        //                _max_order_of_derivatives = 2;
        //                _div_point_count = 500;

        //                _image_icc = _icc->GenerateImage(_max_order_of_derivatives, _div_point_count);
        //                if (!_image_icc)
        //                {
        //                    throw Exception("Could not generate the image of the interpolating cyclic curve!");
        //                }
        //                else
        //                {
        //                    cout << "Image of Icyclic curve was created!\n";
        //                    if (!_image_icc->UpdateVertexBufferObjects())
        //                    {
        //                        throw Exception("Could not generate the vertex buffer object of the interpolating curve's image!");
        //                    }
        //                }
        //            }
        //        }

        // -> Genetic algorithm

        _show = GL_TRUE;

        _n = 2;
        ((MainWindow*)_parent)->_settings_widget->data_point_size_spin_box->setValue(_n);
        _individual_number = 10;
        ((MainWindow*)_parent)->_settings_widget->individual_number_spin_box->setValue(_individual_number);

        _individual_number_to_show = _individual_number;
        ((MainWindow*)_parent)->_settings_widget->individual_number_to_show_spin_box->setValue(_individual_number_to_show);

        _image_of_data_points = 0;

        _data_point_element_index = 0;

        initialDataPoints();
    }

    void GLWidget::initialDataPoints() {

        if (_image_of_data_points)
        {
            delete _image_of_data_points, _image_of_data_points = 0;

            _image_of_data_points = new GenericCurve3(0, 2 * _n + 1);
        }
        else
        {
            _image_of_data_points = new GenericCurve3(0, 2 * _n + 1);
        }


        GLdouble step = 2.0 * PI / (2 * _n + 1);
        _data_point_to_interpolate.ResizeRows(2 * _n + 1);
        ((MainWindow*)_parent)->_settings_widget->data_point_element_combo_box->clear();

        for (GLuint i = 0; i < 2 * _n + 1; ++i)
        {
            ((MainWindow*)_parent)->_settings_widget->data_point_element_combo_box->addItem(QString::number(i),i);

            // we intend to interpolate the control polygon of the original cyclic curve
            DCoordinate3 point;
            GLdouble u = i * step;
            point[0] = 1.0 * cos(u);
            point[1] = 1.0 * sin(u);
            point[2] = 0.0;//-2.0 + 4.0 * (GLdouble)rand() / (GLdouble)RAND_MAX;
            _data_point_to_interpolate[i] = point;

            if (_image_of_data_points)
            {
                (*_image_of_data_points)(0, i) = point;
            }
        }

        if (_image_of_data_points)
        {
            _image_of_data_points->UpdateVertexBufferObjects();
        }

        initialPopulation();
    }

    void GLWidget::initialPopulation() {

        try {
            _population = 0;
            _population = new CyclicCurvePopulation(_individual_number, _n, _data_point_to_interpolate);
        } catch (Exception e) {
            throw e;
        }
    }

    void GLWidget::evolve(int checked) {
        min_length = -1;
        if (checked > 0) {
            //            timer->setInterval(5000);
            timer->start();
        } else {
            timer->stop();
        }

    }

    void GLWidget::animate() {
        if (min_length = -1) {
            RowMatrix<CyclicCurvePopulation::Individual*> inds = _population->getAllIndividual();
            min_length = 0;
            for (GLuint i = 0; i < inds.GetColumnCount(); i++) {
                min_length += inds[i]->getFitness();
            }
        }
        _population->evolve();
        GLdouble act_length = 0;
        RowMatrix<CyclicCurvePopulation::Individual*> inds = _population->getAllIndividual();
        for (GLuint i = 0; i < inds.GetColumnCount(); i++) {
            act_length += inds[i]->getFitness();
        }
        if (act_length < min_length) {
            min_length = act_length;
        }
        if (act_length / (GLdouble)inds.GetColumnCount() < _population->getBest()->getFitness() + 0.01) {
            timer->stop();
            for (GLuint i = 0; i < inds.GetColumnCount(); i++) {
                cout << inds[i]->getFitness() << /*" knots-> " << inds[i]->getKnotsVector() <<*/ endl;
            }
            cout << "stopped!" << endl;
        }
        updateGL();
    }

    // rendering method
    void GLWidget::paintGL()
    {
        // clear the color and depth buffers
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // save (or duplicate) the modelview matrix
        glPushMatrix();

        // perform your transformations and render your geometry
        glRotated(_angle_x, 1.0, 0.0, 0.0);
        glRotated(_angle_y, 0.0, 1.0, 0.0);
        glRotated(_angle_z, 0.0, 0.0, 1.0);

        glTranslated(_trans_x, _trans_y, _trans_z);

        glScaled(_scale, _scale, _scale);

        //            MatFBBrass.Apply();
        //            _mouse.Render();

        if (_cc)
        {
            //                glColor3f(0.7, 0.7, 0.9);
            //                _cc->RenderData(GL_LINE_LOOP);

            //                glPointSize(5.0);
            //                _cc->RenderData(GL_POINTS);
            //                glPointSize(1.0);
        }

        if (_image_cc)
        {
            //                glColor3f(1.0, 0.8, 0.0);
            //                _image_cc->RenderDerivatives(0, GL_LINE_LOOP);

            //                glColor3f(0.0, 1.0, 0.0);
            //                _image_cc->RenderDerivatives(1, GL_LINES);

            //                glColor3f(0.1, 0.5, 1.0);
            //                _image_cc->RenderDerivatives(2, GL_LINES);
        }

        if (_icc)
        {
            //                glColor3f(0.7, 0.7, 0.9);
            //                _icc->RenderData(GL_LINE_LOOP);

            glPointSize(5.0);
            _cc->RenderData(GL_POINTS);
            glPointSize(1.0);
        }

        if (_image_icc)
        {
            //                glColor3f(1.0, 1.0, 1.0);
            //                glColor3f(1.0, 0.8, 0.0);
            //                _image_icc->RenderDerivatives(0, GL_LINE_LOOP);

        }

        if (_show) {

            if (_image_of_data_points)
            {
                glColor3f(0.0, 1.0, 1.0);
                glLineWidth(2.0);
                _image_of_data_points->RenderDerivatives(0, GL_LINE_LOOP);
                glLineWidth(1.0);

                glColor3f(0.0, 1.0, 0.0);
                glPointSize(10.0);
                _image_of_data_points->RenderDerivatives(0, GL_POINTS);
                glPointSize(1.0);
            }
            RowMatrix<CyclicCurvePopulation::Individual*> inds = _population->getAllIndividual();

            glColor3f(1, 0, 0);
            for (int i = 1; i < _individual_number_to_show; i++) {
                inds[i]->getImage()->RenderDerivatives(0, GL_LINE_LOOP);
            }

            CyclicCurvePopulation::Individual * _best = _population->getBest();
            glColor3f(0, 1, 0);
            _best->getImage()->RenderDerivatives(0, GL_LINE_LOOP);
            //                glColor3f(1, 0.7, 0.3);
            //                _best->getImage()->RenderDerivatives(1, GL_LINES);
            //                glColor3f(0.7, 0.2, 0.2);
            //                _best->getImage()->RenderDerivatives(2, GL_LINES);

            glPointSize(5.0);
            _best->getCurve()->RenderData(GL_POINTS);
            glPointSize(1.0);
        }

        // restore the original modelview matrix
        glPopMatrix();
    }

    // resizing method
    void GLWidget::resizeGL(int w, int h)
    {
        // set the viewport
        glViewport(0, 0, w, h);

        // define the projection matrix
        glMatrixMode(GL_PROJECTION);

        _aspect = (GLfloat)w / (GLfloat)h;

        glLoadIdentity();
        gluPerspective(_fovy, _aspect, _z_near, _z_far);

        // revert to the modelview matrix
        glMatrixMode(GL_MODELVIEW);

        // update your scene, i.e., invoke indirectly the method paintGL()
        updateGL();

    }

    // implementation of public event handlers
    void GLWidget::set_angle_x(int value)
    {
        if (_angle_x != (double)value)
        {
            _angle_x = value;
            updateGL();
        }
    }

    void GLWidget::set_angle_y(int value)
    {
        if (_angle_y != (double)value)
        {
            _angle_y = value;
            updateGL();
        }
    }

    void GLWidget::set_angle_z(int value)
    {
        if (_angle_z != (double)value)
        {
            _angle_z = value;
            updateGL();
        }
    }

    void GLWidget::set_trans_x(double value)
    {
        if (_trans_x != value)
        {
            _trans_x = value;
            updateGL();
        }
    }

    void GLWidget::set_trans_y(double value)
    {
        if (_trans_y != value)
        {
            _trans_y = value;
            updateGL();
        }
    }

    void GLWidget::set_trans_z(double value)
    {
        if (_trans_z != value)
        {
            _trans_z = value;
            updateGL();
        }
    }

    void GLWidget::set_zoom_factor(double value)
    {
        if (_scale != value)
        {
            _scale = value;
            updateGL();
        }
    }

    void GLWidget::reset_to_original_view()
    {
        _trans_x = _trans_y = _trans_z = 0.0;
        _scale = 1.0;
        _angle_x = _angle_y = _angle_z = 0.0;

        updateGL();
    }

    void GLWidget::set_data_point_size(int value) {
        if (_n != value)
        {
            _n = value;

            initialDataPoints();

            updateGL();
        }
    }

    void GLWidget::set_individual_number(int value) {
        if ((_individual_number != value) && (_individual_number_to_show <= value))
        {
            _individual_number = value;

            initialPopulation();

            updateGL();
        }
    }

    void GLWidget::set_individual_number_to_show(int value) {
        if ((_individual_number_to_show != value) && (_individual_number >= value))
        {
            _individual_number_to_show = value;

            updateGL();
        }
    }

    void GLWidget::set_data_point_elements(int value) {
        if (((MainWindow*)_parent)->_settings_widget->data_point_element_combo_box->hasFocus())
        {
            _data_point_element_index = value;

            DCoordinate3 tmp = _data_point_to_interpolate[_data_point_element_index];

            ((MainWindow*)_parent)->_settings_widget->data_point_element_x_spin_box->setValue(tmp.x());
            ((MainWindow*)_parent)->_settings_widget->data_point_element_y_spin_box->setValue(tmp.y());
            ((MainWindow*)_parent)->_settings_widget->data_point_element_z_spin_box->setValue(tmp.z());
        }
    }

    void GLWidget::set_data_point_element_x(double value) {

        if (((MainWindow*)_parent)->_settings_widget->data_point_element_x_spin_box->hasFocus())
        {

            if (_data_point_to_interpolate[_data_point_element_index].x() != value)
            {
                _data_point_to_interpolate[_data_point_element_index].x() = value;

                if (_image_of_data_points)
                {
                    (*_image_of_data_points)(0, _data_point_element_index) = _data_point_to_interpolate[_data_point_element_index];
                    _image_of_data_points->UpdateVertexBufferObjects();
                }

                initialPopulation();

                updateGL();
            }
        }
    }

    void GLWidget::set_data_point_element_y(double value) {

        if (((MainWindow*)_parent)->_settings_widget->data_point_element_y_spin_box->hasFocus())
        {

            if (_data_point_to_interpolate[_data_point_element_index].y() != value)
            {
                _data_point_to_interpolate[_data_point_element_index].y() = value;

                if (_image_of_data_points)
                {
                    (*_image_of_data_points)(0, _data_point_element_index) = _data_point_to_interpolate[_data_point_element_index];
                    _image_of_data_points->UpdateVertexBufferObjects();
                }

                initialPopulation();

                updateGL();
            }
        }
    }

    void GLWidget::set_data_point_element_z(double value) {

        if (((MainWindow*)_parent)->_settings_widget->data_point_element_z_spin_box->hasFocus())
        {

            if (_data_point_to_interpolate[_data_point_element_index].z() != value)
            {
                _data_point_to_interpolate[_data_point_element_index].z() = value;

                if (_image_of_data_points)
                {
                    (*_image_of_data_points)(0, _data_point_element_index) = _data_point_to_interpolate[_data_point_element_index];
                    _image_of_data_points->UpdateVertexBufferObjects();
                }

                initialPopulation();

                updateGL();
            }
        }
    }

    void GLWidget::reset_initialization() {
        initialDataPoints();

        updateGL();
    }

    void GLWidget::save_to_file() {
        QString filename = QFileDialog::getSaveFileName(this, tr("Save"), QString(), tr("My files (*.myfile);;All Files(*)"));

        if (!filename.isEmpty()) {
            if (! (filename.endsWith(".myfile", Qt::CaseInsensitive))) {
                filename += ".myfile";
            }

            string tmp = filename.toUtf8().constData();

            fstream f(tmp.c_str(), ios_base::out);

            f << _n << endl;

            f << _individual_number << endl;

            f << _data_point_to_interpolate << endl;

            f.close();

            _message_box.setWindowTitle("Save");
            _message_box.setText("File is saved!");
            _message_box.exec();
        }
    }

    void GLWidget::load_from_file() {
        QString filename = QFileDialog::getOpenFileName(this, tr("Open"), QString(), tr("My files (*.myfile);;All Files(*)"));

        if (!filename.isEmpty()) {

            string tmp = filename.toUtf8().constData();

            fstream f(tmp.c_str(), ios_base::in);

            f >> _n;

            f >> _individual_number;

            f >> _data_point_to_interpolate;

            f.close();

            reset_values();

            initialPopulation();

            updateGL();

            _message_box.setWindowTitle("Open");
            _message_box.setText("File is loaded!");
            _message_box.exec();
        }
    }

    void GLWidget::reset_values() {
        ((MainWindow*)_parent)->_settings_widget->data_point_size_spin_box->setValue(_n);
        ((MainWindow*)_parent)->_settings_widget->data_point_element_x_spin_box->setValue(0.0);
        ((MainWindow*)_parent)->_settings_widget->data_point_element_y_spin_box->setValue(0.0);
        ((MainWindow*)_parent)->_settings_widget->data_point_element_z_spin_box->setValue(0.0);
        ((MainWindow*)_parent)->_settings_widget->individual_number_spin_box->setValue(_individual_number);
        ((MainWindow*)_parent)->_settings_widget->individual_number_to_show_spin_box->setValue(_individual_number);
    }
}

